package ua.kharkov.knure.pereverziev.diploma.db.DAOServices;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;

import ua.kharkov.knure.pereverziev.diploma.db.entity.HasId;

/**
 * Common service that implements common CRUD operations that can be made with
 * entity. Extend your service from this class to have default behavior. Class
 * has to implement HasId interface.
 * 
 * @param <T>
 *            entity type.
 */
public abstract class CommonService<T extends HasId> {
	protected final Class<T> type;

	private static final Logger log = Logger.getLogger(CommonService.class);

	public CommonService(final Class<T> type) {
		this.type = type;
	}

	/**
	 * Persists passed bean to the database.
	 * 
	 * @param newObject
	 *            detached entity that is not persited.
	 * @return id of the newly created bean.
	 */
	public Long create(final T newObject) {
		try {
			HibernateUtils.begin();
			HibernateUtils.getSession().persist(newObject);
			HibernateUtils.commit();
		} catch (final Exception e) {
			log.error("Error while creating entity:" + type.getName() + ". "
					+ e.getMessage());
		}
		return newObject.getId();
	}

	/**
	 * Deletes entity by id.
	 * 
	 * @param id
	 *            id of the entity to delete.
	 */
	public void delete(final Long id) {
		HibernateUtils.begin();
		HibernateUtils.getSession().delete(getReferenceWithId(id, type));
		HibernateUtils.commit();
	}

	/**
	 * Searches database for an entity and gets one if exists.
	 * 
	 * @param id
	 *            the identifier of the persistent instance
	 * 
	 * @return the persistent instance, or null if not found
	 */
	@SuppressWarnings("unchecked")
	public T get(final Long id) {
		return (T) HibernateUtils.getSession().get(type, id);
	}

	/**
	 * Creates proxy object with with specified id without doing request to
	 * database.
	 * 
	 * @param id
	 *            the identifier of the persistent instance
	 * 
	 * @return the persistent instance, or null if not found
	 */
	@SuppressWarnings("unchecked")
	public T load(final Long id) {
		return (T) HibernateUtils.getSession().load(type, id);
	}

	/**
	 * Queries for entities by example entity.
	 * 
	 * @param qbe
	 *            example entity to find.
	 * @return list of entities or empty list if none found.
	 */
	@SuppressWarnings("unchecked")
	public List<T> getList(final T qbe) {
		return getCriteria(qbe).list();
	}

	/**
	 * Queries for entities by example entity.
	 * 
	 * @param qbe
	 *            example entity to find.
	 * @return list of entities or empty list if none found.
	 */
	@SuppressWarnings("unchecked")
	public Map<Long, T> getMap(final T qbe) {
		final List<T> list = getCriteria(qbe).list();
		final Map<Long, T> result = new HashMap<Long, T>(list.size(), 0.9f);
		for (final T item : list) {
			result.put(item.getId(), item);
		}
		return null;
	}

	/**
	 * Queries for entities by example entity.
	 * 
	 * @param qbe
	 *            example entity to find.
	 * @return list of entities or empty list if none found.
	 */
	@SuppressWarnings("unchecked")
	public List<T> getList(final T qbe, final Criterion criterion) {
		return getCriteria(qbe).add(criterion).list();
	}

	/**
	 * Constructs default criteria. Used to create default example.
	 * 
	 * @param qbe
	 *            the example entity.
	 * @param excludeProperties
	 *            exclude properties for the entity. Define any if you don't
	 *            want to search by the property and want to add criteria by
	 *            yourself. E.g. you want to add like expression intead of
	 *            equals to name.
	 * @return criteria with added example entity.
	 */
	public Criteria getCriteria(final T qbe, final String... excludeProperties) {
		final Example example = Example.create(qbe).excludeZeroes();
		for (final String f : excludeProperties) {
			example.excludeProperty(f);
		}
		return HibernateUtils.getSession().createCriteria(qbe.getClass()).add(example)
				.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
	}

	/**
	 * Updates detached entity in the database.
	 * 
	 * @param transientObject
	 *            entity to populate to database.
	 */
	public void merge(final T transientObject) {
		HibernateUtils.begin();
		HibernateUtils.getSession().merge(transientObject);
		HibernateUtils.commit();
	}

	public void save(final T transientObject) {
		HibernateUtils.getSession().saveOrUpdate(transientObject);
	}

	public static <I extends HasId> I getReferenceWithId(final Long id,
			final Class<I> clazz) {
		I result;
		try {
			result = clazz.newInstance();
		} catch (final Exception e) {
			// should never happen
			throw new IllegalArgumentException(
					"Can't instantiate object instance.", e);
		}
		result.setId(id);
		return result;
	}

}
